/**
 * @file
 * @brief RTD：XWDS：I2C
 * @author
 * + 隐星魂 (Roy.Sun) <https://xwos.tech>
 * @copyright
 * + (c) 2015 隐星魂 (Roy.Sun) <https://xwos.tech>
 * > Licensed under the Apache License, Version 2.0 (the "License");
 * > you may not use this file except in compliance with the License.
 * > You may obtain a copy of the License at
 * >
 * >         http://www.apache.org/licenses/LICENSE-2.0
 * >
 * > Unless required by applicable law or agreed to in writing, software
 * > distributed under the License is distributed on an "AS IS" BASIS,
 * > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * > See the License for the specific language governing permissions and
 * > limitations under the License.
 */

#include "board/std.h"
#include <xwos/osal/time.h>
#include <xwos/osal/sync/cond.h>
#include <xwos/osal/lock/spinlock.h>
#include <xwcd/ds/i2c/master.h>
#include "Flexio_Mcl_Ip.h"
#include "Flexio_I2c_Ip.h"

struct rtdxwds_flexioi2cm_driver_data {
        xwu8_t instance;
        xwu8_t channel;
        const Flexio_Ip_InstanceConfigType * flexiocfg;
        const Flexio_I2c_Ip_MasterConfigType * i2cmcfg;
        struct {
                struct xwos_splk lock;
                struct xwos_cond completion;
        } xfer;
};

static
xwer_t rtdxwds_flexioi2cm_drv_probe(struct xwds_device * dev);

static
xwer_t rtdxwds_flexioi2cm_drv_remove(struct xwds_device * dev);

static
xwer_t rtdxwds_flexioi2cm_drv_start(struct xwds_device * dev);

static
xwer_t rtdxwds_flexioi2cm_drv_stop(struct xwds_device * dev);

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t rtdxwds_flexioi2cm_drv_suspend(struct xwds_device * dev);

static
xwer_t rtdxwds_flexioi2cm_drv_resume(struct xwds_device * dev);
#endif

static
xwer_t rtdxwds_flexioi2cm_drv_xfer(struct xwds_i2cm * i2cm,
                                   struct xwds_i2c_msg * msg,
                                   xwtm_t to);
static
xwer_t rtdxwds_flexioi2cm_drv_abort(struct xwds_i2cm * i2cm,
                                    xwu16_t address, xwu16_t addrmode,
                                    xwtm_t to);

struct xwds_i2cm_driver rtdxwds_flexioi2cm_drv = {
        .base = {
                .name = "rtdxwds.flexio.i2cm",
                .probe = rtdxwds_flexioi2cm_drv_probe,
                .remove = rtdxwds_flexioi2cm_drv_remove,
                .start = rtdxwds_flexioi2cm_drv_start,
                .stop =  rtdxwds_flexioi2cm_drv_stop,
#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
                .suspend = rtdxwds_flexioi2cm_drv_suspend,
                .resume =  rtdxwds_flexioi2cm_drv_resume,
#endif
        },
        .xfer = rtdxwds_flexioi2cm_drv_xfer,
        .abort = rtdxwds_flexioi2cm_drv_abort,
};

static
xwer_t rtdxwds_flexioi2cm_drv_probe(struct xwds_device * dev)
{
        struct xwds_i2cm * i2cm;
        struct rtdxwds_flexioi2cm_driver_data * drvdata;

        i2cm = xwds_cast(struct xwds_i2cm *, dev);
        drvdata = i2cm->dev.data;
        xwos_splk_init(&drvdata->xfer.lock);
        xwos_cond_init(&drvdata->xfer.completion);
        return XWOK;
}

static
xwer_t rtdxwds_flexioi2cm_drv_remove(struct xwds_device * dev)
{
        struct xwds_i2cm * i2cm;
        struct rtdxwds_flexioi2cm_driver_data * drvdata;

        i2cm = xwds_cast(struct xwds_i2cm *, dev);
        drvdata = i2cm->dev.data;
        xwos_cond_fini(&drvdata->xfer.completion);
        return XWOK;
}

static
xwer_t rtdxwds_flexioi2cm_drv_start(struct xwds_device * dev)
{
        struct xwds_i2cm * i2cm;
        struct rtdxwds_flexioi2cm_driver_data * drvdata;
        Flexio_Ip_CommonStatusType flexst;
        Flexio_I2c_Ip_StatusType i2cmst;
        xwer_t rc;

        i2cm = xwds_cast(struct xwds_i2cm *, dev);
        drvdata = i2cm->dev.data;
        flexst = Flexio_Mcl_Ip_InitDevice(drvdata->flexiocfg);
        if (FLEXIO_IP_COMMON_STATUS_SUCCESS != flexst) {
                rc = -EINVAL;
                goto err_flexio_init;
        }
        i2cmst = Flexio_I2c_Ip_MasterInit(drvdata->instance,
                                          drvdata->channel,
                                          drvdata->i2cmcfg);
        if (FLEXIO_I2C_IP_SUCCESS_STATUS != i2cmst) {
                rc = -EINVAL;
                goto err_i2cm_init;
        }

        return XWOK;

err_i2cm_init:
err_flexio_init:
        return rc;
}

static
xwer_t rtdxwds_flexioi2cm_drv_stop(struct xwds_device * dev)
{
        struct xwds_i2cm * i2cm;
        struct rtdxwds_flexioi2cm_driver_data * drvdata;

        i2cm = xwds_cast(struct xwds_i2cm *, dev);
        drvdata = i2cm->dev.data;
        Flexio_I2c_Ip_MasterDeinit(drvdata->instance, drvdata->channel);
        Flexio_Mcl_Ip_DeinitDevice(drvdata->instance);
        return XWOK;
}

#if defined(XWCDCFG_ds_PM) && (1 == XWCDCFG_ds_PM)
static
xwer_t rtdxwds_flexioi2cm_drv_suspend(struct xwds_device * dev)
{
        return rtdxwds_flexioi2cm_drv_stop(dev);
}

static
xwer_t rtdxwds_flexioi2cm_drv_resume(struct xwds_device * dev)
{
        return rtdxwds_flexioi2cm_drv_start(dev);
}
#endif

xwer_t rtdxwds_flexioi2cm_drv_xfer(struct xwds_i2cm * i2cm,
                                   struct xwds_i2c_msg * msg,
                                   xwtm_t to)
{
        struct rtdxwds_flexioi2cm_driver_data * drvdata;
        bool stop;
        Flexio_I2c_Ip_StatusType st;
        xwer_t rc;
        xwreg_t cpuirq;
        union xwos_ulock lock;
        xwsq_t lkst;

        drvdata = i2cm->dev.data;
        lock.osal.splk = &drvdata->xfer.lock;
        stop = ((xwu16_t)XWDS_I2C_F_STOP & msg->flag) ? true : false;
        xwos_splk_lock_cpuirqsv(&drvdata->xfer.lock, &cpuirq);
        st = Flexio_I2c_Ip_MasterSetSlaveAddr(drvdata->instance,
                                              drvdata->channel,
                                              (msg->addr >> (xwu16_t)1));
        if (FLEXIO_I2C_IP_SUCCESS_STATUS != st) {
                rc = -EBUSY;
                goto err_busy;
        }
        if ((xwu16_t)XWDS_I2C_F_RD & msg->flag) {
                st = Flexio_I2c_Ip_MasterReceiveData(drvdata->instance,
                                                     drvdata->channel,
                                                     msg->data,
                                                     msg->size,
                                                     stop);
        } else {
                st = Flexio_I2c_Ip_MasterSendData(drvdata->instance,
                                                  drvdata->channel,
                                                  msg->data,
                                                  msg->size,
                                                  stop);
        }
        if (FLEXIO_I2C_IP_SUCCESS_STATUS != st) {
                switch (st) {
                case FLEXIO_I2C_IP_TX_UNDERRUN_STATUS:
                case FLEXIO_I2C_IP_RX_OVERRUN_STATUS:
                        rc = -EOVERFLOW;
                        break;
                case FLEXIO_I2C_IP_ARBITRATION_LOST_STATUS:
                        rc = -EAGAIN;
                        break;
                case FLEXIO_I2C_IP_ABORTED_STATUS:
                        rc = -ECONNABORTED;
                        break;
                case FLEXIO_I2C_IP_BUSY_STATUS:
                case FLEXIO_I2C_IP_BUS_BUSY_STATUS:
                        rc = -EBUSY;
                        break;
                case FLEXIO_I2C_IP_ERROR_STATUS:
                case FLEXIO_I2C_IP_RECEIVED_NACK_STATUS:
                case FLEXIO_I2C_IP_TIMEOUT_STATUS:
                case FLEXIO_I2C_IP_UNSUPPORTED_STATUS:
                case FLEXIO_I2C_IP_DMA_ERROR_STATUS:
                default:
                        rc = -EIO;
                        break;
                }
                goto err_xfer;
        }
        rc = xwos_cond_wait_to(&drvdata->xfer.completion, lock, XWOS_LK_SPLK,
                               NULL, to, &lkst);
        if (XWOK == rc) {
                XWOS_BUG_ON((xwsq_t)XWOS_LKST_UNLOCKED == lkst);
                st = Flexio_I2c_Ip_MasterGetStatus(drvdata->instance,
                                                   drvdata->channel,
                                                   NULL);
                xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
                switch (st) {
                case FLEXIO_I2C_IP_SUCCESS_STATUS:
                        rc = XWOK;
                        break;
                case FLEXIO_I2C_IP_TX_UNDERRUN_STATUS:
                case FLEXIO_I2C_IP_RX_OVERRUN_STATUS:
                        rc = -EOVERFLOW;
                        break;
                case FLEXIO_I2C_IP_ARBITRATION_LOST_STATUS:
                        rc = -EAGAIN;
                        break;
                case FLEXIO_I2C_IP_ABORTED_STATUS:
                        rc = -ECONNABORTED;
                        break;
                case FLEXIO_I2C_IP_BUSY_STATUS:
                case FLEXIO_I2C_IP_BUS_BUSY_STATUS:
                        rc = -EBUSY;
                        break;
                case FLEXIO_I2C_IP_ERROR_STATUS:
                case FLEXIO_I2C_IP_RECEIVED_NACK_STATUS:
                case FLEXIO_I2C_IP_TIMEOUT_STATUS:
                case FLEXIO_I2C_IP_UNSUPPORTED_STATUS:
                case FLEXIO_I2C_IP_DMA_ERROR_STATUS:
                default:
                        rc = -EIO;
                        break;
                }
        } else {
                if ((xwsq_t)XWOS_LKST_LOCKED == lkst) {
                        xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
                } else {
                        xwos_cpuirq_restore_lc(cpuirq);
                }
        }
        return XWOK;

err_xfer:
err_busy:
        xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
        return rc;
}

xwer_t rtdxwds_flexioi2cm_drv_abort(struct xwds_i2cm * i2cm,
                                    xwu16_t address, xwu16_t addrmode,
                                    xwtm_t to)
{
        struct rtdxwds_flexioi2cm_driver_data * drvdata;
        xwreg_t cpuirq;

        XWOS_UNUSED(address);
        XWOS_UNUSED(addrmode);
        XWOS_UNUSED(to);

        drvdata = i2cm->dev.data;
        xwos_splk_lock_cpuirqsv(&drvdata->xfer.lock, &cpuirq);
        Flexio_I2c_Ip_MasterTransferAbort(drvdata->instance, drvdata->channel);
        xwos_splk_unlock_cpuirqrs(&drvdata->xfer.lock, cpuirq);
        return XWOK;
}

/******** ******** flexioi2cm ******** ********/
struct rtdxwds_flexioi2cm_driver_data rtdxwds_flexioi2cm_drvdata = {
        .instance = FLEXIO_0,
        .channel = 0,
        .flexiocfg = &Flexio_Ip_xFlexioInit,
        .i2cmcfg = &Flexio_I2cMasterChannel0_BOARD_InitPeripherals,
};

struct xwds_i2cm rtdxwds_flexioi2cm = {
        /* attributes */
        .dev = {
                .name = "rtdxwds.flexio.i2cm",
                .id = 0,
                .resources = NULL,
                .drv = xwds_cast(struct xwds_driver *, &rtdxwds_flexioi2cm_drv),
                .data = (void *)&rtdxwds_flexioi2cm_drvdata,
        },
};

/******** ******** Callback ******** ********/
struct xwds_i2cm * const rtdxwds_flexioi2cm_devices[] = {
        [0] = &rtdxwds_flexioi2cm,
};

void rtdxwds_flexioi2cm_callback(Flexio_I2c_Ip_MasterEventType event, uint8 data)
{
        struct xwds_i2cm * i2cm;
        struct rtdxwds_flexioi2cm_driver_data * drvdata;

        XWOS_UNUSED(event);
        i2cm = rtdxwds_flexioi2cm_devices[data];
        drvdata = i2cm->dev.data;
        xwos_cond_broadcast(&drvdata->xfer.completion);
}
